/* 
 * Copyright (C) 2013, 2014 lex xiang
 *
 * file:	drivers/tty.c
 * history:	2013 Juy 11 created by lex
 */
#include "tty.h"
#include "uart.h"
#include <stdarg.h>
#include <stdio.h>

struct tty g_tty;

struct tty * tty_alloc()
{
	struct tty *tty;

	//return tty = (struct tty *)malloc(sizeof(struct tty));
	return tty = &g_tty;
}

void tty_free(struct tty *tty)
{
	//free(tty);
	return ;
}

u8 tty_get_char(struct tty * tty)
{
	return uart_getc(tty->uart_port);
}

void tty_put_char(struct tty * tty, u8 ch)
{
	uart_putc(ch, tty->uart_port);
	return;
}

void tty_put_string(struct tty *tty, char *string)
{
	uart_puts(string, tty->uart_port);
	return;
}

void tty_printf(struct tty *tty, char *fmt, ...)
{
	char buf[NR_PRINT_CH_MAX];
	va_list arg;

	va_start(arg, fmt);
	vsnprintf(buf, NR_PRINT_CH_MAX, fmt, arg);
	va_end(arg);

	uart_puts((u8 *)buf, tty->uart_port);

	return;
}

s32 tty_get_line(struct tty *tty, u8 *buf, s32 size)
{
	u8 ch;

	while (!tty->done) {
		ch = tty->op.getc(tty);

		if (ch > 0 && ch < 128 && tty->op.ch_handler[ch]) {
			tty->op.ch_handler[ch](tty, ch);
			tty->op.redisplay(tty);
		}
	}
	tty->done = 0;

	return tty->op.read_line(tty, buf, size);
}

s32 tty_read_line(struct tty *tty, u8 *buf, s32 size)
{
	u32 i, j;

	for (i = tty->beg, j = 0; i < tty->end && j < size; ++i, ++j)
		buf[j] = tty->line[i];
	buf[j] = '\0';

	return j;
}

static void tty_redisplay(struct tty *tty)
{
	u32 off, el, cl;

	if (!tty->modified)
		return;

	tty->op.puts(tty, &tty->line[tty->prev]);
	cursor_end_clear(tty);

	tty->prev     = tty->cursor;
	tty->modified = 0;

	if (!(off = tty->end - tty->cursor))
		return;

	if ((el = tty->end / TTY_COL_MAX) != (cl = tty->cursor / TTY_COL_MAX)) {
		cursor_backward(tty, tty->end % TTY_COL_MAX);
		cursor_up(tty, el - cl);
		cursor_forward(tty, tty->cursor % TTY_COL_MAX);
	} else {
		cursor_backward(tty, off);
	}

	return;
}

void tty_save_pos(struct tty *tty, u8 *str)
{
	s32 len = strlen(str);

	strncpy(tty->line, str, len);
	tty->cursor	= len;
	tty->prev	= len;
	tty->beg	= len;
	tty->end	= len;

	return;
}

void tty_drain(struct tty *tty)
{
	tty->cursor = tty->beg;
	tty->prev   = tty->beg;
	tty->end    = tty->beg;

	tty->line[tty->end] = '\0';

	return;
}

s32 tty_parse_line(struct tty *tty, u8 ch)
{
	return 0;
}

static s32 tty_insert_char(struct tty *tty, u8 ch)
{
	if (tty->end + 1 > TTY_BUF_MAX)
		return -1;

	if (tty->cursor < tty->end)
		memmove(&tty->line[tty->cursor + 1], &tty->line[tty->cursor], 
			tty->end - tty->cursor + 1);
	else
		tty->line[tty->end + 1] = '\0';

	tty->line[tty->cursor] = ch;
	tty->cursor++;
	tty->end++;
	tty->modified = 1;

	return 0;
}

static s32 tty_insert_str(struct tty *tty, u8 *str)
{
	u32 len = strlen(str);

	if (tty->end + len > TTY_BUF_MAX)
		return -1;

	if (tty->end > tty->cursor)
		memmove(&tty->line[tty->cursor + len], &tty->line[tty->cursor],
			tty->end - tty->cursor + 1);
	else 
		tty->line[tty->end + len + 1] = '\0';

	strncpy(&tty->line[tty->cursor], str, len);

	tty->cursor  += len;
	tty->end     += len;
	tty->modified = 1;

	return 0;
}

static s32 tty_doch_default(struct tty *tty, u8 ch)
{
	if (tty_insert_char(tty, ch) < 0)
		return -1;

	return 0;
}

static s32 tty_delete_char(struct tty *tty, u8 ch)
{
	if (tty->cursor < tty->beg)
		return -1;

	if (tty->cursor < tty->end) {
		memmove(&tty->line[tty->cursor], &tty->line[tty->cursor + 1], 
			tty->end - tty->cursor + 1);

		tty->end--;
		tty->line[tty->end] = '\0';
		tty->modified = 1;
	}
	
	return 0;
}

static s32 tty_doch_backspace(struct tty *tty, u8 ch)
{
	if (tty->cursor <= tty->beg)
		return -1;

	if (tty->cursor < tty->end)
		memmove(&tty->line[tty->cursor - 1], &tty->line[tty->cursor], 
			tty->end - tty->cursor + 1);
	else
		tty->line[tty->end - 1] = '\0';

	cursor_backward(tty, 1);
	cursor_erase(tty, 1);
	tty->cursor--;
	tty->end--;
	tty->prev = tty->cursor;

	tty->modified = 1;
	return 0;
}
/*
 * table
 */
static s32 tty_doch_ht(struct tty *tty, u8 ch)
{
	if (tty->op.hook[HOOK_HT])
		tty->op.hook[HOOK_HT](tty->op.hook_arg);

	return 0;
}

/*
 * move to start of line C-a	^A
 */
static s32 tty_doch_soh(struct tty *tty, u8 ch)
{
	s32 line, col;

	if (tty->cursor <= tty->beg)
		return -1;

	if (tty->cursor > TTY_COL_MAX) {
		cursor_backward(tty, tty->cursor % TTY_COL_MAX);
		cursor_up(tty, tty->cursor / TTY_COL_MAX);
		cursor_forward(tty, tty->beg);
	} else {
		cursor_backward(tty, tty->cursor - tty->beg);
	}
	tty->prev = tty->cursor = tty->beg;

	return 0;
}

/*
 * backward C-b	^B
 */
static s32 tty_doch_stx(struct tty *tty, u8 ch)
{
	if (tty->cursor <= tty->beg)
		return -1;

	if (!((tty->cursor - 1) % TTY_COL_MAX)) {
		cursor_up(tty, 1);
		cursor_forward(tty, TTY_COL_MAX - 1);
	} else {
		cursor_backward(tty, 1);
	}

	tty->cursor--;
	tty->prev = tty->cursor;

	return 0;
}

/*
 * end of line	C-e	^E,
 */
static s32 tty_doch_enq(struct tty *tty, u8 ch)
{
	u32 off;
	if (tty->cursor >= tty->end)
		return -1;

	if (tty->end > TTY_COL_MAX) {
		cursor_down(tty, tty->end / TTY_COL_MAX - tty->cursor / TTY_COL_MAX);
		if ((off = tty->cursor + TTY_COL_MAX) > tty->end)
			cursor_backward(tty, off - tty->end);
		else
			cursor_forward(tty, tty->end - off);
	} else {
		cursor_forward(tty, tty->end - tty->cursor);
	}

	tty->prev = tty->cursor = tty->end;

	return 0;
}

/*
 * forward	C-f	^F
 */
static s32 tty_doch_ack(struct tty *tty, u8 ch)
{
	if (tty->cursor >= tty->end)
		return -1;

	if (!(tty->cursor % TTY_COL_MAX)) {
		cursor_down(tty, 1);
		cursor_backward(tty, TTY_COL_MAX - 1);
	} else {
		cursor_forward(tty, 1);
	}

	tty->cursor++;
	tty->prev = tty->cursor;
	return 0;
}

/*
 * kill the text from cursor to end C-k	^K
 */
static s32 tty_doch_vt(struct tty *tty, u8 ch)
{
	if (tty->cursor >= tty->end)
		return -1;

	tty->killed_sz = tty->end - tty->cursor;
	strncpy(tty->killed, &tty->line[tty->cursor], tty->killed_sz);
	tty->killed[tty->killed_sz] = '\0';

	cursor_end_clear(tty);
	tty->prev = tty->end = tty->cursor;
	tty->line[tty->end + 1] = '\0';

	return 0;
}

/*
 * carriage return \r	C-m	^M
 */
static s32 tty_doch_cr(struct tty *tty, u8 ch)
{
	tty->done = 1;
	tty->op.dev_printf(tty, "\n");

	return 0;
}

/*
 * next	command	C-n	^N
 */
static s32 tty_doch_so(struct tty *tty, u8 ch)
{
	if (tty->op.hook[HOOK_SO])
		tty->op.hook[HOOK_SO];

	return 0;
}

/*
 * previous command	C-p	^P
 */
static s32 tty_doch_dle(struct tty *tty, u8 ch)
{
	if (tty->op.hook[HOOK_DLE])
		tty->op.hook[HOOK_DLE];

	return 0;
}

/*
 * yank	C-y	^Y
 */
static s32 tty_doch_em(struct tty *tty, u8 ch)
{
	if (tty->end + tty->killed_sz > TTY_BUF_MAX || !tty->killed_sz)
		return -1;
	tty->op.insert_str(tty, tty->killed);
	return 0;
}

/*
 * Escape	^[
 */
static s32 tty_doch_esc(struct tty *tty, u8 ch)
{
	tty->op.dev_printf("%d", ch);
	return 0;
}


s32 tty_init(struct tty *tty, int uart_port,
		 fp_tty_getc getc, fp_tty_putc putc, fp_tty_puts puts,
		 fp_tty_printf printf)
{
	s32 i;

	tty->uart_port	= uart_port;
	tty->done	= 0;
	tty->cursor	= 0;
	tty->end	= 0;
	tty->beg	= 0;
	tty->killed_sz	= 0;

	tty->op.getc		= getc;
	tty->op.putc		= putc;
	tty->op.puts		= puts;
	tty->op.dev_printf	= printf;
	tty->op.save_pos	= tty_save_pos;
	tty->op.get_line	= tty_get_line;
	tty->op.insert_str	= tty_insert_str;
	tty->op.redisplay	= tty_redisplay;
	tty->op.drain		= tty_drain;
	tty->op.read_line	= tty_read_line;

	for (i = 0; i < NR_ASCII_CODE; ++i)
		tty->op.ch_handler[i]	= (fp_tty_ch_handler)0;
	for (i = 32; i < CH_DEL; ++i)
		tty->op.ch_handler[i]	= tty_doch_default;

	tty->op.ch_handler[CH_SOH]	= tty_doch_soh;
	tty->op.ch_handler[CH_STX]	= tty_doch_stx;
	tty->op.ch_handler[CH_EOT]	= tty_delete_char;
	tty->op.ch_handler[CH_ENQ]	= tty_doch_enq;
	tty->op.ch_handler[CH_ACK]	= tty_doch_ack;
	tty->op.ch_handler[CH_VT]	= tty_doch_vt;
	tty->op.ch_handler[CH_CR]	= tty_doch_cr;
	tty->op.ch_handler[CH_SO]	= tty_doch_so;
	tty->op.ch_handler[CH_DLE]	= tty_doch_dle;
	tty->op.ch_handler[CH_EM]	= tty_doch_em;
	tty->op.ch_handler[CH_BS]	= tty_doch_backspace;
	tty->op.ch_handler[CH_HT]	= tty_doch_ht;
	tty->op.ch_handler[CH_DEL]	= tty_delete_char;

	for (i = 0; i < NR_HOOK_MAX; ++i)
		tty->op.hook[i]	= (fp_tty_misc)0;

	return 0;
}

